home *** CD-ROM | disk | FTP | other *** search
/ NeXT Education Software Sampler 1992 Fall / NeXT Education Software Sampler 1992 Fall.iso / Programming / Source / Tuner / TunerApp.m < prev    next >
Encoding:
Text File  |  1992-06-10  |  10.6 KB  |  444 lines

  1. /* TunerApp.m -- copyright 1992 by C.D.Lane */
  2.  
  3. #import "TunerApp.h"
  4.  
  5. #import <dsp/arrayproc.h>
  6.  
  7. #define APPLICATION "Tuner"
  8. #define VERSION __DATE__
  9.  
  10. #define RTF_SEGMENT "__RTF"
  11. #define HELP_SECTION "Tuner.rtf"
  12.  
  13. #define freq(k) MKKeyNumToFreq(k)
  14. #define keyNum(f) MKFreqToKeyNum(f, NULL, 0.0)
  15.  
  16. #define writeDefault(n, v) NXWriteDefault(APPLICATION, n, v)
  17. #define getStringDefault(n) NXGetDefaultValue(APPLICATION, n)
  18. #define getIntDefault(n) atoi(NXGetDefaultValue(APPLICATION, n))
  19. #define getFloatDefault(n) atof(NXGetDefaultValue(APPLICATION, n))
  20.  
  21. #define TONES (12)
  22.  
  23. #define RECORDDELAY (0.25)
  24.  
  25. #define FFT_SIZE (1024)
  26.  
  27. #define FFT_DATA DSPAPGetLowestAddressXY()
  28. #define FFT_COEF (FFT_DATA + FFT_SIZE)
  29. #define FFT_SKIP (1)
  30. #define DATA_SKIP (2)
  31.  
  32. #define FFT_IMAG DSPMapPMemY(FFT_DATA)
  33. #define FFT_REAL DSPMapPMemX(FFT_DATA)
  34.  
  35. #define SIN_TABLE DSPMapPMemY(FFT_COEF)
  36. #define COS_TABLE DSPMapPMemX(FFT_COEF)
  37.  
  38. typedef enum {ZERO = 0, FFT} METHODS;
  39. typedef enum {LINEAR = -1, INDEXED = 0} ADDR_MODES;
  40.  
  41. @implementation TunerApp : Application
  42.  
  43. void stopRecord(DPSTimedEntry timedEntry, double now, id self)
  44. {
  45.     [self stop:self];
  46. }
  47.  
  48. void startRecord(DPSTimedEntry timedEntry, double now, id self)
  49. {
  50.     int status;
  51.  
  52.     if((status = [self record]) != SND_ERR_NONE) [NXApp printf:"record: %s\n", SNDSoundError(status)];
  53. }
  54.  
  55. - appDidInit:sender
  56. {
  57.     [defaults registerDefaults:APPLICATION];
  58.  
  59.     [[[[soundView setAutoscale:YES] setDisplayMode:SK_DISPLAY_WAVE] setAutodisplay:YES] setSound:sound];
  60.  
  61.     key = lastKey = c00k;
  62. #ifdef DEBUG    
  63.     (void) DSPSetErrorFP(stderr);
  64.     (void) DSPEnableErrorLog();
  65. #endif
  66.     [self loadDefaults:sender];
  67.  
  68.     timedEntry = DPSAddTimedEntry(RECORDDELAY, (DPSTimedEntryProc) &startRecord, sound, NX_BASETHRESHOLD);
  69.  
  70.     return self;
  71. }
  72.  
  73. - free
  74. {
  75.     if(timedEntry != NULL) DPSRemoveTimedEntry(timedEntry);
  76. #ifdef DEBUG    
  77.     (void) DSPDSPDisableErrorLog();
  78. #endif
  79.     return [super free];
  80. }
  81.  
  82. - willRecord:sender
  83. {
  84.     [soundMeter run:sender];
  85.  
  86.     if(timedEntry != NULL) DPSRemoveTimedEntry(timedEntry);
  87.  
  88.     timedEntry = DPSAddTimedEntry([timeSlider floatValue], (DPSTimedEntryProc) &stopRecord, sound, NX_BASETHRESHOLD);
  89.  
  90.     return self;
  91. }
  92.  
  93. - didRecord:sender
  94. {
  95.     id result = nil;
  96.     int status = SND_ERR_NONE;
  97.  
  98.     if(timedEntry != NULL) DPSRemoveTimedEntry(timedEntry);
  99.     timedEntry = NULL;
  100.  
  101.     amplitude = [[soundMeter stop:sender] peakValue];
  102.  
  103.     if((status = [sample copySound:sound]) == SND_ERR_NONE) {
  104.         if((status = [sound deleteSamples]) == SND_ERR_NONE) {
  105.             if(amplitude >= [squelchSlider floatValue]) {            
  106.                 if((status = [sample convertToFormat:SND_FORMAT_LINEAR_16]) == SND_ERR_NONE) {
  107.                     switch([[methodMatrix selectedCell] tag]) {
  108.                         case FFT : result = [self computeFrequencyViaFFT]; break;
  109.                         case ZERO : default : result = [self computeFrequency]; break;
  110.                         }
  111.                     }
  112.                 else [self printf:"convertToFormat: %s\n", SNDSoundError(status)];
  113.                 }
  114.             else [[self clearNoteName] clearCentError];
  115.             }
  116.         else [self printf:"deleteSamples: %s\n", SNDSoundError(status)];
  117.         }
  118.     else [self printf:"copySound: %s\n", SNDSoundError(status)];
  119.  
  120.     if(result != nil) {
  121.         frequency *= [adjustmentSlider floatValue];
  122. #ifdef DEBUG
  123.         [self printf:"amplitude = %f\n", amplitude];
  124.         [self printf:"frequency = %f\n\n", frequency];
  125. #endif
  126.         if((key = keyNum(frequency)) % TONES == lastKey) [[self showNoteName] showCentError];
  127.         else [[self clearNoteName] clearCentError];
  128.  
  129.         lastKey = key % TONES;
  130.         }
  131.  
  132.     timedEntry = DPSAddTimedEntry(RECORDDELAY, (DPSTimedEntryProc) &startRecord, sound, NX_BASETHRESHOLD);
  133.  
  134.     return self;
  135. }
  136.  
  137. - windowDidBecomeKey:sender
  138. {
  139.     int size;
  140.     void *pointer;
  141.     NXStream *stream;
  142.     static BOOL flag = NO;
  143.  
  144.     if(flag) return self;
  145.  
  146.     if((pointer = getsectdata(RTF_SEGMENT, HELP_SECTION, &size)) == NULL) return nil;
  147.  
  148.     if((stream = NXOpenMemory((const char *) pointer, size, NX_READONLY)) == NULL) return nil;
  149.  
  150.     [[helpScrollView docView] readRichText:stream];
  151.  
  152.     NXCloseMemory(stream, NX_FREEBUFFER);
  153.  
  154.     flag = YES;
  155.  
  156.     return self;
  157. }
  158.  
  159. - computeFrequency
  160. {
  161.     short *pointer = (short *) [sample data];
  162.     unsigned int start, end = 0, i = 0, transitions = 0, size = [sample sampleCount];
  163.  
  164.     while(i < size && pointer[end = i++] == 0);
  165.  
  166.     while(i < size && !((pointer[end] > 0 && pointer[i] < 0) || (pointer[end] < 0 && pointer[i] > 0))) ++i;
  167.  
  168.     end = i++;
  169.  
  170.     if(i >= size) return nil;
  171.  
  172.     for(start = i; i < size; i++)
  173.         if((pointer[end] > 0 && pointer[i] < 0) || (pointer[end] < 0 && pointer[i] > 0)) {
  174.             transitions++;
  175.             end = i;
  176.             }
  177.  
  178.     if(start > end) return nil;
  179.  
  180.     frequency = (transitions * [sample samplingRate]) / (2 * ((end - start) + 1));
  181.  
  182.     return self;
  183. }
  184.  
  185. - computeFrequencyViaFFT
  186. {
  187.     unsigned int i;
  188.     MKKeyNum note, maximum = c00k;
  189.     float pitch, pitches[b7k], spectrum[FFT_SIZE];
  190.     short data[FFT_SIZE], hits[b7k], *pointer = (short *) [sample data];
  191.     float threshold = [squelchSlider floatValue], rate = [sample samplingRate] / (FFT_SIZE * DATA_SKIP);
  192.  
  193.     if([sample sampleCount] < (FFT_SIZE * DATA_SKIP)) return nil;
  194.  
  195.     for(i = 0; i < FFT_SIZE; i++) data[i] = pointer[i * DATA_SKIP];
  196.  
  197.     for(note = c00k; note < b7k; note++) pitches[note] = hits[note] = 0;
  198.  
  199.     if(DSPAPInit() == 0) {
  200.         (void) DSPAPWriteFloatArray(DSPAPSinTable(FFT_SIZE), SIN_TABLE, 1, FFT_SIZE/2);
  201.         (void) DSPAPWriteFloatArray(DSPAPCosTable(FFT_SIZE), COS_TABLE, 1, FFT_SIZE/2);
  202.  
  203.         (void) DSPAPWriteShortArray(data, FFT_REAL, 1, FFT_SIZE);
  204.  
  205.         (void) DSPAPvclear(FFT_IMAG, 1, FFT_SIZE);
  206.  
  207.         (void) DSPAPfftr2a(FFT_SIZE, FFT_DATA, FFT_COEF);
  208.  
  209.         (void) DSPSetDMAReadMReg(INDEXED); {
  210.             (void) DSPAPReadFloatArray(spectrum, FFT_IMAG, FFT_SIZE/2, FFT_SIZE);
  211.             } (void) DSPSetDMAReadMReg(LINEAR);
  212.  
  213.         (void) DSPAPFree();
  214.         }
  215.     else return [self switchMethod:"Switching methods, DSP not available!"];
  216.  
  217.     for(i = 0; i < FFT_SIZE; i++)
  218.         if(fabs(spectrum[i]) >= threshold) {
  219.             note = keyNum(pitch = (i * rate));
  220.             pitches[note] += pitch;
  221.             ++hits[note];
  222.             }
  223.  
  224.     for(note = c00k; note < b7k; note++) if(hits[note] > hits[maximum]) maximum = note;
  225.  
  226.     if(hits[maximum] == 0) return nil;
  227.  
  228.     frequency = pitches[maximum] / hits[maximum];
  229.  
  230.     return self;
  231. }
  232.  
  233. - showNoteName
  234. {
  235.     unsigned int i, size;
  236.     id control, list = [buttonMatrix cellList];
  237.  
  238.     size = [list count];
  239.  
  240.     for(i = 0; i < size; i++) {
  241.         control = [list objectAt:i];
  242.         [control setEnabled:(((key + [transpositionField intValue]) % TONES) == [control tag])];
  243.         }
  244.  
  245.     return self;
  246. }
  247.  
  248. - clearNoteName
  249. {
  250.     unsigned int i, size;
  251.     id list = [buttonMatrix cellList];
  252.  
  253.     size = [list count];
  254.  
  255.     for(i = 0; i < size; i++) [[list objectAt:i] setEnabled:NO];
  256.  
  257.     return self;
  258. }
  259.  
  260. - showCentError
  261. {
  262.     double error, zero, minimum, maximum, correct, calibration = [calibrationField floatValue] / freq(a4k);
  263.  
  264.     correct = freq(key) * calibration;
  265.     minimum = freq(key - 1) * calibration;
  266.     maximum = freq(key + 1) * calibration;
  267.  
  268.     [[[centSlider setMinValue:minimum] setMaxValue:maximum] setFloatValue:frequency];
  269.  
  270.     [centSlider setEnabled:YES];
  271.  
  272.     zero = (((error = frequency - correct) > 0.0) ? maximum - correct : correct - minimum) * [toleranceSlider floatValue];
  273.  
  274.     [flat setState:(error <= zero)];
  275.     [sharp setState:(error >= -zero)];
  276.     [attune setState:([flat state] && [sharp state])];
  277.  
  278.     return self;
  279. }
  280.  
  281. - clearCentError
  282. {
  283.     [[[[centSlider setMinValue:freq(af4k)] setMaxValue:freq(as4k)] setFloatValue:freq(a4k)] setEnabled:NO];
  284.  
  285.     [flat setState:NO];
  286.     [sharp setState:NO];
  287.     [attune setState:NO];
  288.  
  289.     return self;
  290. }
  291.  
  292. - switchMethod:(const char *) reason
  293. {
  294.     (void) NXRunAlertPanel(APPLICATION, reason, NULL, NULL, NULL);
  295.  
  296.     [methodMatrix selectCellWithTag:ZERO];
  297.  
  298.     return nil;
  299. }
  300.  
  301. - setDefault:sender
  302. {
  303.     [sender setTag:YES];
  304.  
  305.     return self;
  306. }
  307.  
  308. - setCalibration:sender
  309. {
  310.     [calibrationField setIntValue:[[sender selectedCell] tag]];
  311.  
  312.     [sender setTag:YES];
  313.  
  314.     return self;
  315. }
  316.  
  317. - setTransposition:sender
  318. {
  319.     [transpositionField setIntValue:[[sender selectedCell] tag]];
  320.  
  321.     [sender setTag:YES];
  322.  
  323.     return self;
  324. }
  325.  
  326. - printf:(const char *) format, ...
  327. {
  328.     va_list ap;
  329.  
  330.     va_start(ap, format); {
  331.         (void) vfprintf(stderr, format, ap);
  332.         } va_end(ap);
  333.  
  334.     return self;
  335. }
  336.  
  337. - loadDefaults:sender;
  338. {
  339.     const char *string;
  340.     unsigned int i, size;
  341.     id cell, list = [methodMatrix cellList];
  342.  
  343.     [[squelchSlider setTag:NO] setFloatValue:getFloatDefault("Squelch")];
  344.     [[timeSlider setTag:NO] setFloatValue:getFloatDefault("SampleTime")];
  345.     [[toleranceSlider setTag:NO] setFloatValue:getFloatDefault("Tolerance")];
  346.     [[adjustmentSlider setTag:NO] setFloatValue:getFloatDefault("Adjustment")];
  347.     [calibrationField setIntValue:getIntDefault("Calibration")];
  348.     [[calibrationMatrix setTag:NO] selectCellWithTag:[calibrationField intValue]];
  349.     [transpositionField setIntValue:getIntDefault("Transposition")];
  350.     [[transpositionMatrix setTag:NO] selectCellWithTag:[transpositionField intValue]];
  351.  
  352.     for(i = 0, size = [list count], string = getStringDefault("Method"); i < size; i++)
  353.         if(strcmp([(cell = [list objectAt:i]) title], string) == 0) [methodMatrix selectCell:cell];
  354.     [methodMatrix setTag:NO];
  355.  
  356.     return self; 
  357. }
  358.  
  359. - saveDefaults:sender
  360. {
  361.     if([squelchSlider tag]) (void) writeDefault("Squelch", [squelchSlider stringValue]);
  362.     if([timeSlider tag]) (void) writeDefault("SampleTime", [timeSlider stringValue]);
  363.     if([toleranceSlider tag]) (void) writeDefault("Tolerance", [toleranceSlider stringValue]);
  364.     if([adjustmentSlider tag]) (void) writeDefault("Adjustment", [adjustmentSlider stringValue]);
  365.     if([calibrationMatrix tag]) (void) writeDefault("Calibration", [calibrationField stringValue]);
  366.     if([transpositionMatrix tag]) (void) writeDefault("Transposition", [transpositionField stringValue]);
  367.     if([methodMatrix tag]) (void) writeDefault("Method", [[methodMatrix selectedCell] title]);
  368.  
  369.     return self;
  370. }
  371.  
  372. - resetDefaults:sender
  373. {
  374.     [defaults updateDefaults];
  375.  
  376.     return [self loadDefaults:sender];
  377. }
  378.  
  379. - setVersion:anObject
  380. {
  381.     [(version = anObject) setStringValue:VERSION];
  382.  
  383.     return self;
  384. }
  385.  
  386. @end
  387.  
  388. @implementation Sound(SimpleMethods)
  389.  
  390. - (int)    convertToFormat:(int) aFormat
  391. {
  392.     return [self convertToFormat:aFormat samplingRate:[self samplingRate] channelCount:[self channelCount]];
  393. }
  394.  
  395. @end
  396.  
  397. @implementation NXStringTable(DefaultsTable)
  398.  
  399. - registerDefaults:(const char *) owner
  400. {
  401.     if(![self applyToDefaults:owner function:&NXRegisterDefaults]) return nil;
  402.  
  403.     return self;
  404. }
  405.  
  406. - writeDefaults:(const char *) owner
  407. {
  408.     if(![self applyToDefaults:owner function:&NXWriteDefaults]) return nil;
  409.  
  410.     return self;
  411. }
  412.  
  413. - updateDefaults
  414. {
  415.     NXUpdateDefaults();
  416.  
  417.     return self;
  418. }
  419.  
  420. - (int) applyToDefaults:(const char *) owner function:(int (*)(const char *, const NXDefaultsVector)) routine
  421. {
  422.     int i, status;
  423.     const void *key, *value;
  424.     struct _NXDefault *vector;
  425.     NXHashState state = [self initState];
  426.  
  427.     if((vector = (struct _NXDefault *) calloc((size_t) ([self count] + 1), sizeof(struct _NXDefault))) == NULL) perror("calloc");
  428.  
  429.     for(i = 0; [self nextState:&state key:&key value:&value]; i++) {
  430.          vector[i].name = (char *) key;
  431.          vector[i].value = (char *) value;
  432.          }
  433.  
  434.     vector[i].name = vector[i].value = NULL;
  435.  
  436.     status = (*routine)(owner, vector);
  437.  
  438.     cfree(vector);
  439.  
  440.     return status;
  441. }
  442.  
  443. @end
  444.